/*******************************************************************************************
 * Copyright (c) 2006-2009 Alex Robson, Rob Simmons, Craig Israel, and Rachel Twyford
 *
 * Use of this software in any form requires that you have both 
 * read and agreed to the following terms:
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions: 
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * Neither the name of Nvigorate nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************************/

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Nvigorate.Common;
using Nvigorate.Common.Reflection;
using Nvigorate.Data.Query;
using Nvigorate.Extensions;
using Nvigorate.Relational.Mapping;
using Nvigorate.Relational.Mapping.Index;
using Nvigorate.Relational.Mapping.Interface;
using Nvigorate.Relational.Record;
using lookupKey = Nvigorate.Common.Tuple<System.Type, string>;

namespace Nvigorate.Relational.Repository
{
    #region Hide
    /// <summary>
    /// Don't touch me, I'm too complex for you : )
    /// </summary>
    //public class OLD_PersistenceKeyManager : IPersistenceKeyManager
    //{
    //    #region Fields

    //    protected static readonly string PERSISTENCE_TICKET = "PersistenceTicket";
    //    protected static readonly string KEY_NAME = "key";
    //    protected static readonly lookupKey nullKey = Tuple.Create(typeof(object), "");

    //    protected IRepositoryMapIndex _repositoryMaps;
    //    protected Dictionary<Type, TableVariable> _tableVariables = new Dictionary<Type, TableVariable>();
    //    protected Dictionary<lookupKey, string> _variables = new Dictionary<lookupKey, string>();
    //    protected List<lookupKey> _foreignKeys = new List<lookupKey>();

    //    protected Dictionary<Guid, IPersistable> _persisted = new Dictionary<Guid, IPersistable>();
    //    #endregion

    //    public Dictionary<Guid, IPersistable> Persisted
    //    {
    //        get { return _persisted; }
    //    }

    //    #region Protected

    //    protected IEnumerable<string> GetKeyList(Type type, string source)
    //    {
    //        var map = _repositoryMaps.GetMap(type);
    //        var keyAssignments = map
    //            .Sources
    //            .Values.Where(s => s.SourceName == source || source == "")
    //            .SelectMany(s => s.Assignments.Where(a => a.Source.PrimaryKey));

    //        var inheritedKeys = 
    //            keyAssignments.Select(a =>
    //                 map.GetAssignmentsByPredicate(c =>
    //                                               c.Target.Field == a.Target.Field &&
    //                                               c.Source.Field != c.Source.Field &&
    //                                               c.Source.Entity != c.Source.Entity)).Flatten();

    //        var keyList = keyAssignments
    //                    .Select(a => a.Target.Field);

    //        var foreignKeys = _foreignKeys.Where(f => f.Value1.Equals(type)).Select(t => t.Value2);
    //        return keyList.Concat(foreignKeys).Distinct();
    //    }

    //    protected void AddVariable(Type type, string property)
    //    {
    //        var key = Tuple.Create(type, property);

    //        if(_variables.ContainsKey(key))
    //            return;

    //        var index = _variables.Keys.Count() + 1;
    //        _variables.Add(key, KEY_NAME + index);
    //    }

    //    public lookupKey GetLookupKey(Type type, IAssignment assignment, IPersistable persistable)
    //    {
    //        var property = assignment.Target.Field;

    //        // if the assignment is the primary key for the instance
    //        // then we want to simply persist that value assuming it's
    //        // not an identity or rowguid (if it were it shouldn't get here)
    //        if(assignment.Source.PrimaryKey)
    //        {
    //            return nullKey;
    //        }

    //        var key = Tuple.Create(type, property);

    //        // if the key is present in the variables dictionary
    //        // but not in the foreign key list
    //        // then populate the value from this type's key table
    //        if(_variables.ContainsKey(key) && !_foreignKeys.Contains(key))
    //        {
    //            return key;
    //        }

    //        // if the instance is null then we need go no further as
    //        // there won't be a relationship
    //        if(persistable == null)
    //        {
    //            return nullKey;
    //        }

    //        var parentType = persistable.InstanceType;
    //        var parentMap = _repositoryMaps.GetMap(parentType);
    //        var childMap = _repositoryMaps.GetMap(type);
    //        var relationship = parentMap.GetRelationshipForType<OneToManyRelationship>(type) ?? 
    //                            childMap.GetRelationshipForType<OneToManyRelationship>(parentType);

    //        // if no relationship then reflect from the source instance
    //        // to get the value for the current property/column
    //        if (relationship == null)
    //        {
    //            return nullKey; // null key
    //        }
    //        key = (relationship.Constraints.ContainsValue(property)) ?
    //                    Tuple.Create(relationship.SubjectType, relationship.Constraints.Where(c => c.Value == property).First().Key) :
    //                    nullKey; // this property doesn't map to an FK

    //        // double check to ensure the foreign key relationship isn't the
    //        // result of back-reference to the parent
    //        if(key == nullKey)
    //        {
    //            key = (relationship.Constraints.ContainsKey(property)) ?
    //                    Tuple.Create(relationship.SubjectType, relationship.Constraints.Where(c => c.Key == property).First().Value) :
    //                    nullKey; // this property doesn't map to an FK
    //        }

    //        return key;
    //    }

    //    protected IQueryObject GetOutputClauseValue(ISourceMap sourceMap, string keyName, string rowName)
    //    {
    //        var assignment =
    //            sourceMap.Assignments.FirstOrDefault(
    //                a => a.Target.Field == keyName);
    //        if (assignment == null)
    //        {
    //            return QueryValue.Reference("null");
    //        }
    //        else
    //        {
    //            return Output.Column(assignment.Source.Field).OwnedBy(rowName);
    //        }
    //    }

    //    protected void PopulateForeignKeys(Type type, IBindingMap map)
    //    {
    //        var relationship = map.GetRelationshipForType<OneToManyRelationship>(type);
    //        if(relationship != null)
    //        {
    //            _foreignKeys.AddRange(relationship.Constraints.Select(c => Tuple.Create(relationship.RelativeType, c.Value)));
    //        }

    //        var keyAssignments = map
    //            .Sources
    //            .Values
    //            .SelectMany(s => s.Assignments.Where(a => a.Source.PrimaryKey));

    //        var inheritedKeys =
    //            keyAssignments.Select(a =>
    //                 map.GetAssignmentsByPredicate(c =>
    //                                               c.Target.Field == a.Target.Field &&
    //                                               c.Source.Entity != a.Source.Entity)).Flatten();

    //        _foreignKeys.AddRange(inheritedKeys.Select(i => Tuple.Create(Reflector.GetType(i.Target.Entity), i.Target.Field)));
    //    }

    //    protected IQueryObject SetVariableToKey(Type type, string property, IPersistable persistable)
    //    {
    //        var key = Tuple.Create(type, property);
    //        var variable = _variables[key];
    //        var select = GetKeySelectStatement(type, property, persistable);
    //        return Variable.Named(variable).Set(select);
    //    }

    //    #endregion

    //    #region Public

    //    public IQueryObject GetInsertIntoKeyTable(IPersistable persistable)
    //    {
    //        var type = persistable.InstanceType;
    //        var keys = GetKeyList(type, "");
    //        return
    //            Insert.Query(_tableVariables[type].Table, 
    //                         new QueryList()
    //                             .Add(Insert.Column(PERSISTENCE_TICKET))
    //                             .AddRange(keys.Select<string, IQueryObject>(k => Insert.Column(k))),
    //                         new QueryList()
    //                             .Add(QueryValue.Reference(string.Format("'{0}'", persistable.PersistenceTicket)))
    //                             .AddRange(keys.Select<string, IQueryObject>(k => QueryValue.Reference(string.Format("'{0}'", Reflector.Read(persistable.Instance, k)))))
    //                );
    //    }

    //    public IEnumerable<string> GetKeyList(Type type, ISourceMap sourceMap)
    //    {
    //        return GetKeyList(type, sourceMap.SourceName);
    //    }

    //    public Select GetKeySelectStatement(Type type, string property, IPersistable persistable)
    //    {
    //        return Select.Query(
    //            Table.Variable(_tableVariables[type].Table.Name),
    //            Column.Named(property),
    //            Where.Criteria(
    //                Criterion.Column(PERSISTENCE_TICKET, "")
    //                    .EqualTo(QueryValue.Reference(string.Format("'{0}'", persistable.PersistenceTicket))) &
    //                Criterion.Column(property, "").NotEqualTo((Literal)null)
    //                )
    //            ).Top(QueryValue.Reference(1));
    //    }

    //    public string GetKeyVariable(Type type, IAssignment assignment, IPersistable persistable)
    //    {
    //        var key = GetLookupKey(type, assignment, persistable);
    //        return _variables.ContainsKey(key) ? _variables[key] : null;
    //    }

    //    public Output GetOutputClauseForSource(IPersistable persistable, ISourceMap sourceMap, string rowName)
    //    {
    //        var type = persistable.InstanceType;
    //        var keyList = GetKeyList(type, "");

    //        var outputColumns = new QueryList()
    //                                .Add(QueryValue.Reference(string.Format("'{0}'", persistable.PersistenceTicket)))
    //                                .AddRange(keyList.Select(k => GetOutputClauseValue(sourceMap, k, rowName)));

    //        return new Output(outputColumns, _tableVariables[type].Table);
    //    }

    //    public IQueryObject GetTableDeclarations()
    //    {
    //        return _tableVariables.Values.ToQueryList();
    //    }

    //    public IQueryObject GetTableSelection()
    //    {
    //        return _tableVariables.Values.Select(t => 
    //                Select.Query(t.Table, 
    //                    t.ColumnList.Select(c =>
    //                        {
    //                            Column column = c as SchemaColumn;
    //                            return Function.Max(Column.Named(column.Name)).As(column.Name);
    //                        }).ToQueryList(),
    //                        Group.By(Column.Named(PERSISTENCE_TICKET))
    //                    )
    //            ).ToQueryList();
    //    }

    //    public IEnumerable<string> GetTableVariableNames()
    //    {
    //        return _tableVariables.Values.Select(t => t.Table.Name);
    //    }

    //    public QueryList GetVariableAssignments(Type type, ISourceMap sourceMap, IPersistable parent)
    //    {
    //        var lookupKeys = sourceMap.Assignments
    //            .Select(a => GetLookupKey(type, a, parent))
    //            .Where(k => k != nullKey);

    //        return lookupKeys.Select(k => SetVariableToKey(k.Value1, k.Value2, parent)).ToQueryList();
    //    }

    //    public IQueryObject GetVariableDeclarations()
    //    {
    //        var keyList = _variables
    //            .Select(v => v.Value);
    //        return keyList.Select(k => Variable.Declare(k, typeof(string))).ToQueryList();
    //    }

    //    public void PopulateKeys(DataSet dataSet)
    //    {
    //        foreach (DataTable table in dataSet.Tables)
    //        {
    //            var rows = (DataRecordCollection) table;
    //            rows.ForEach(r =>
    //                {
    //                    var key = new Guid(r[PERSISTENCE_TICKET].ToString());
    //                    r.FieldNames
    //                      .Where(f => f != PERSISTENCE_TICKET)
    //                      .ForEach(f => Reflector.Write(_persisted[key].Instance, f, r[f]));
    //                });
    //        }
    //    }

    //    public void RegisterPersistableInstance(IPersistable persistable)
    //    {
    //        if (!_persisted.ContainsKey(persistable.PersistenceTicket))
    //            _persisted.Add(persistable.PersistenceTicket, persistable);
    //    }

    //    public void RegisterType(Type type)
    //    {
    //        if (IsRegistered(type)) return;

    //        var map = _repositoryMaps.GetMap(type);
    //        if (map == null) return;
            
    //        PopulateForeignKeys(type, map);
    //        var keyList = GetKeyList(type, "");
    //        var tableVariable = new TableVariable(
    //            Table.Variable(type.Name),
    //            new QueryList()
    //                .Add(TableVariable.Column(PERSISTENCE_TICKET).Type(typeof(string)))
    //                .AddRange(keyList.Select(k => TableVariable.Column(k).Type(typeof(string))).Cast<IQueryObject>())
    //            );
    //        _tableVariables.Add(type, tableVariable);
    //        keyList.ForEach(p => AddVariable(map.TargetType, p));
    //    }

    //    #endregion

    //    public OLD_PersistenceKeyManager(IRepositoryMapIndex repositoryMaps)
    //    {
    //        _repositoryMaps = repositoryMaps;
    //    }

    //    #region Private Methods

    //    private bool IsRegistered(Type type)
    //    {
    //        return _tableVariables.ContainsKey(type);
    //    }
    //    #endregion
    //}
    #endregion

    /// <summary>
    /// Don't touch me, I'm too complex for you : )
    /// </summary>
    public class PersistenceKeyManager : IPersistenceKeyManager
    {
        #region Fields

        protected static readonly string PERSISTENCE_TICKET = "PersistenceTicket";
        protected static readonly string KEY_NAME = "key";
        protected static readonly lookupKey nullKey = Tuple.Create(typeof(object), "");

        protected IRepositoryMapIndex _repositoryMaps;
        protected Dictionary<Type, TableVariable> _tableVariables = new Dictionary<Type, TableVariable>();
        protected Dictionary<lookupKey, string> _variables = new Dictionary<lookupKey, string>();
        protected Dictionary<lookupKey, lookupKey> _foreignKeys = new Dictionary<lookupKey, lookupKey>();

        protected Dictionary<Guid, IPersistable> _persisted = new Dictionary<Guid, IPersistable>();
        #endregion

        public Dictionary<Guid, IPersistable> Persisted
        {
            get { return _persisted; }
        }

        #region Protected

        protected void AddForeignKey(lookupKey foreignKey, lookupKey primaryKey)
        {
            if(!_foreignKeys.ContainsKey(foreignKey))
            {
                _foreignKeys.Add(foreignKey, primaryKey);
            }
        }

        protected IEnumerable<string> GetKeyList(Type type, string source, bool filterEmptyKeys)
        {
            var map = _repositoryMaps.GetMap(type);
            var keyAssignments = map
                .Sources
                .Values.Where(s => s.SourceName == source || source == "")
                .SelectMany(s => s.Assignments.Where(a => a.PrimaryKey));

            var keyList = keyAssignments
                        .Select(a => a.Target.Field);

            var foreignKeys = _foreignKeys.Keys.Where(f => f.Value1.Equals(type)).Select(t => t.Value2);
            return keyList.Concat(foreignKeys).Where(k => filterEmptyKeys ? !string.IsNullOrEmpty(k) : true).Distinct();
        }

        protected void AddVariable(Type type, string property)
        {
            var key = Tuple.Create(type, property);

            if (_variables.ContainsKey(key))
                return;

            var index = _variables.Keys.Count() + 1;
            _variables.Add(key, KEY_NAME + index);
        }

        public lookupKey GetLookupKey(Type type, IAssignment assignment)
        {
            var property = assignment.Target.Field;

            // if the assignment is the primary key for the instance
            // then we want to simply persist that value assuming it's
            // not an identity or rowguid (if it were it shouldn't get here)
            if (assignment.PrimaryKey)
            {
                return nullKey;
            }

            var key = Tuple.Create(type, property);

            // if the key is present in the variables dictionary
            // but not in the foreign key list
            // then populate the value from this type's key table
            if (_variables.ContainsKey(key) && !_foreignKeys.ContainsKey(key))
            {
                return key;
            }

            if(_foreignKeys.ContainsKey(key) && _variables.ContainsKey(_foreignKeys[key]))
            {
                return _foreignKeys[key];
            }

            // if the instance is null then we need go no further as
            // there won't be a relationship
            //if (persistable == null)
            //{
            //    return nullKey;
            //}

            //var parentType = persistable.InstanceType;
            //var parentMap = _repositoryMaps.GetMap(parentType);
            //var childMap = _repositoryMaps.GetMap(type);
            //var relationship = parentMap.GetRelationshipForType<OneToManyRelationship>(type) ??
            //                    childMap.GetRelationshipForType<OneToManyRelationship>(parentType);

            //// if no relationship then reflect from the source instance
            //// to get the value for the current property/column
            //if (relationship == null)
            //{
            //    return nullKey; // null key
            //}
            //key = (relationship.Constraints.ContainsValue(property)) ?
            //            Tuple.Create(relationship.SubjectType, relationship.Constraints.Where(c => c.Value == property).First().Key) :
            //            nullKey; // this property doesn't map to an FK

            //// double check to ensure the foreign key relationship isn't the
            //// result of back-reference to the parent
            //if (key == nullKey)
            //{
            //    key = (relationship.Constraints.ContainsKey(property)) ?
            //            Tuple.Create(relationship.SubjectType, relationship.Constraints.Where(c => c.Key == property).First().Value) :
            //            nullKey; // this property doesn't map to an FK
            //}

            return nullKey;
        }

        protected IQueryObject GetOutputClauseValue(ISourceMap sourceMap, string keyName, string rowName)
        {
            var assignment =
                sourceMap.Assignments.FirstOrDefault(
                    a => a.Target.Field == keyName);
            if (assignment == null)
            {
                return QueryValue.Reference("null");
            }
            else
            {
                return Output.Column(assignment.Source.Field).OwnedBy(rowName);
            }
        }

        protected void PopulateForeignKeys(Type type, IBindingMap map)
        {
            var oneToManyRelationships = map.Relationships.OfType<OneToManyRelationship>();
            if (oneToManyRelationships != null)
            {
                oneToManyRelationships
                    .ForEach(r =>
                        {
                            foreach (var constraint in r.Constraints)
                            {
                                var subjectKey = Tuple.Create(r.SubjectType, constraint.Key);
                                var relativeKey = Tuple.Create(r.RelativeType, constraint.Value);
                                if (IsPrimaryKey(map, constraint.Key))
                                    AddForeignKey(relativeKey, subjectKey);
                                else
                                    AddForeignKey(subjectKey, relativeKey);
                            }
                        });
            }

            var manyToManyRelationships = map.Relationships.OfType<ManyToManyRelationship>();
            if (manyToManyRelationships != null)
            {
                manyToManyRelationships
                    .ForEach(r =>
                    {
                        var subjectsToRelatives = r.Container.SubjectPairs.Zip(r.Container.RelativePairs);
                        foreach (var constraint in subjectsToRelatives)
                        {
                            var subjectKey = Tuple.Create(r.SubjectType, "");
                            var relativeKey = Tuple.Create(r.RelativeType, constraint.Value2.Target);
                            AddForeignKey(subjectKey, relativeKey);
                        }
                    });
            }

            //var keyAssignments = map
            //    .Sources
            //    .Values
            //    .SelectMany(s => s.Assignments.Where(a => a.PrimaryKey));

            //    keyAssignments
            //        .ForEach(k =>
            //            {
            //                var childAssignments = map.GetAssignmentsByPredicate(c =>
            //                                                                 c.Target.Field == k.Target.Field &&
            //                                                                 c.Source.Entity != k.Source.Entity);

            //            });
        }

        protected bool IsPrimaryKey(IBindingMap map, string targetField)
        {
            return map.GetAssignmentsByTargetField(targetField).Any(a => a.PrimaryKey);
        }

        protected IQueryObject SetVariableToKey(Type type, string property, IPersistable persistable)
        {
            var key = Tuple.Create(type, property);
            var variable = _variables[key];
            var select = GetKeySelectStatement(type, property, persistable);
            return Variable.Named(variable).Set(select);
        }

        #endregion

        #region Public

        public IQueryObject GetInsertIntoKeyTable(IPersistable persistable)
        {
            var type = persistable.InstanceType;
            var keys = GetKeyList(type, "", true);
            return
                Insert.Query(_tableVariables[type].Table,
                             new QueryList()
                                 .Add(Insert.Column(PERSISTENCE_TICKET))
                                 .AddRange(keys.Select<string, IQueryObject>(k => Insert.Column(k))),
                             new QueryList()
                                 .Add(QueryValue.Reference(string.Format("'{0}'", persistable.PersistenceTicket)))
                                 .AddRange(keys.Select<string, IQueryObject>(k => QueryValue.Reference(string.Format("'{0}'", Reflector.Read(persistable.Instance, k)))))
                    );
        }

        public IEnumerable<string> GetKeyList(Type type, ISourceMap sourceMap)
        {
            return GetKeyList(type, sourceMap.SourceName, true);
        }

        public Select GetKeySelectStatement(Type type, string property, IPersistable persistable)
        {
            return Select.Query(
                Table.Variable(_tableVariables[type].Table.Name),
                Column.Named(property),
                Where.Criteria(
                    Criterion.Column(PERSISTENCE_TICKET, "")
                        .EqualTo(QueryValue.Reference(string.Format("'{0}'", persistable.GetRelativeTicket(type, property)))) &
                    Criterion.Column(property, "").NotEqualTo((Literal)null)
                    )
                ).Top(QueryValue.Reference(1));
        }

        public string GetKeyVariable(Type type, IAssignment assignment)
        {
            var key = GetLookupKey(type, assignment);
            return _variables.ContainsKey(key) ? _variables[key] : null;
        }

        public Output GetOutputClauseForSource(IPersistable persistable, ISourceMap sourceMap, string rowName)
        {
            var type = persistable.InstanceType;
            var keyList = GetKeyList(type, "", true);

            var outputColumns = new QueryList()
                                    .Add(QueryValue.Reference(string.Format("'{0}'", persistable.PersistenceTicket)))
                                    .AddRange(keyList.Select(k => GetOutputClauseValue(sourceMap, k, rowName)));

            return new Output(outputColumns, _tableVariables[type].Table);
        }

        public IQueryObject GetTableDeclarations()
        {
            return _tableVariables.Values.ToQueryList();
        }

        public IQueryObject GetTableSelection()
        {
            return _tableVariables.Values.Select(t =>
                    Select.Query(t.Table,
                        t.ColumnList.Select(c =>
                        {
                            Column column = c as SchemaColumn;
                            return Function.Max(Column.Named(column.Name)).As(column.Name);
                        }).ToQueryList(),
                            Group.By(Column.Named(PERSISTENCE_TICKET))
                        )
                ).ToQueryList();
        }

        public IEnumerable<string> GetTableVariableNames()
        {
            return _tableVariables.Values.Select(t => t.Table.Name);
        }

        public QueryList GetVariableAssignments(Type type, ISourceMap sourceMap, IPersistable persistable)
        {
            var lookupKeys = sourceMap.Assignments
                .Select(a => GetLookupKey(type, a))
                .Where(k => k != nullKey);

            return lookupKeys.Select(k => SetVariableToKey(k.Value1, k.Value2, persistable)).ToQueryList();
        }

        public IQueryObject GetVariableDeclarations()
        {
            var keyList = _variables
                .Select(v => v.Value);
            return keyList.Select(k => Variable.Declare(k, typeof(string))).ToQueryList();
        }

        public void PopulateKeys(DataSet dataSet)
        {
            foreach (DataTable table in dataSet.Tables)
            {
                var rows = (DataRecordCollection)table;
                rows.ForEach(r =>
                {
                    var key = new Guid(r[PERSISTENCE_TICKET].ToString());
                    r.FieldNames
                      .Where(f => f != PERSISTENCE_TICKET)
                      .ForEach(f => Reflector.Write(_persisted[key].Instance, f, r[f]));
                });
            }
        }

        public void RegisterPersistableInstance(IPersistable persistable)
        {
            if (!_persisted.ContainsKey(persistable.PersistenceTicket))
                _persisted.Add(persistable.PersistenceTicket, persistable);
        }

        public void RegisterType(Type type)
        {
            if (IsRegistered(type)) return;

            var map = _repositoryMaps.GetMap(type);
            if (map == null) return;

            PopulateForeignKeys(type, map);
            var keyList = GetKeyList(type, "", false);
            var tableVariable = new TableVariable(
                Table.Variable(type.Name),
                new QueryList()
                    .Add(TableVariable.Column(PERSISTENCE_TICKET).Type(typeof(string)))
                    .AddRange(keyList.Where(k => !string.IsNullOrEmpty(k)).Select(k => TableVariable.Column(k).Type(typeof(string))).Cast<IQueryObject>())
                );
            _tableVariables.Add(type, tableVariable);
            keyList.ForEach(p => AddVariable(map.TargetType, p));
        }

        #endregion

        public PersistenceKeyManager(IRepositoryMapIndex repositoryMaps)
        {
            _repositoryMaps = repositoryMaps;
        }

        #region Private Methods

        private bool IsRegistered(Type type)
        {
            return _tableVariables.ContainsKey(type);
        }
        #endregion
    }
}
